/**
 * 有限状态机
 */

// var sm = new app.lib.fsm.create('test', {
//     init: {
//         event: 'init',
//         state: 'solid',
//         data: {}
//     },
//     transitions: [
//         { event: 'melt', state: 'liquid', data: {}, from: 'solid' },
//         { event: 'freeze', state: 'solid', from: 'liquid', force: false },
//         { event: 'vaporize', state: 'gas', from: 'liquid' },
//         { event: 'condense', state: 'liquid', from: 'gas' }
//     ]
// });
// sm.on('onleave:init', function (data) { console.log('onleaveinit', data) });
// sm.on('onleave:melt', function (data) { console.log('onleavemelt', data) });
// sm.on('onleave:freeze', function (data) { console.log('onleavefreeze', data) });
// sm.on('onleave:vaporize', function (data) { console.log('onleavevaporize', data) });
// sm.on('onleave:condense', function (data) { console.log('onleavecondense', data) });

// sm.on('onenter:init', function (data) { console.log('onenterinit', data) });
// sm.on('onenter:melt', function (data) { console.log('onentermelt', data) });
// sm.on('onenter:freeze', function (data) { console.log('onenterfreeze', data) });
// sm.on('onenter:vaporize', function (data) { console.log('onentervaporize', data) });
// sm.on('onenter:condense', function (data) { console.log('onentercondense', data) });

// sm.on('beforeenter:melt', function (data, next) { console.log('afterentermelt', data); return 'ASYNC'; });
// sm.on('beforeenter:freeze', function (data, next) { console.log('afterentermelt', data); return 'ASYNC'; });

// sm.init();
// sm.do('melt');

/**
 * 分为before、on、after3个阶段，其中before和after阶段可以被强制取消，而on阶段默认不可强制取消。
 * force默认是false，当force为true时，可强制取消from的on阶段
 */

function createUUID() {
    var d = new Date().getTime();
    var uuid = 'xxxxxxxx-yyyy-xxxx-yyyy-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
    return uuid;
};

enum ENTER {
    None = 0,
    Front = 1,
    Middle = 2,
    Back = 3
}

interface Initialization {
    event: string,
    state: string,
    data?: any
}

interface Transition {
    event: string,
    state: string,
    data?: any,
    from: string,
    force?: boolean
}

interface Transitions {
    [event: string]: Transition[];
}

interface Events {
    [event: string]: [Function, any, boolean][];
}

interface StateMachineOption {
    init: Initialization,
    transitions: Transition[]
}

class StateMachine {
    private uuid = '';

    private events: Events = {};

    private initialization: Initialization = null;
    private transitions: Transitions = {};

    private currentTransition: Transition | Initialization = null;
    private entering = ENTER.None;

    constructor(option?: StateMachineOption) {
        option && this._init(option);
    }

    private _init(option: StateMachineOption) {
        if (this.initialization) {
            throw new Error('不可重复初始化数据');
        }

        this.initialization = option.init;

        option.transitions.forEach((transition) => {
            if (!this.transitions[transition.event]) {
                this.transitions[transition.event] = [];
            }
            this.transitions[transition.event].push(transition);
        })
    }

    private _leave(oldTransition: Transition | Initialization, newTransition: Transition, finish?: Function) {
        if (this.currentTransition === oldTransition) {
            this.currentTransition = null;

            const event = oldTransition.event;
            const data = {
                curr: oldTransition,
                to: newTransition
            };

            this._emit('beforeleave', event, data);
            this._emit('onleave', event, data);
            this._emit('afterleave', event, data);

            finish && finish();
        } else {
            finish && finish();
        }
    }

    private _enter(oldTransition: Transition | Initialization, newTransition: Transition | Initialization, finish?: Function) {
        const uuid = this.uuid;

        this.currentTransition = newTransition;

        const event = newTransition.event;
        const data = {
            from: oldTransition,
            curr: newTransition
        };

        // 前摇期
        this.entering = ENTER.Front;
        this._emit('beforeenter', event, data, () => {
            if (this.uuid != uuid) return;
            // 中摇期
            this.entering = ENTER.Middle;
            this._emit('onenter', event, data, () => {
                // 后摇期
                this.entering = ENTER.Back;
                this._emit('afterenter', event, data, () => {
                    if (this.uuid != uuid) return;
                    this.entering = 0;
                    finish && finish();
                });
            });
        });
    }

    public get ASYNC() { return 'ASYNC'; }

    public getState() {
        return this.currentTransition.state;
    }

    public isState(state: string) {
        return state === this.getState();
    }

    private _get(event?: string, from?: string, entering?: number) {
        if (typeof from !== 'string') {
            from = this.getState();
        }
        if (typeof entering !== 'string') {
            entering = this.entering;
        }

        if (event) {
            const transitionArr = this.transitions[event];
            return transitionArr.find(function (transition) {
                if (transition.from === from) {
                    return entering !== ENTER.Middle ? true : (transition.force === true);
                }
            })
        } else {
            for (const key in this.transitions) {
                const result: Transition = this._get(key);
                if (result) return result;
            }
        }

        return null;
    }

    public can(event: string) {
        return !!this._get(event);
    }

    public init(option: StateMachineOption, finish?: Function): any;
    public init(finish?: Function): any;
    public init(...args) {
        if (args[0] && typeof args[0] !== 'function') {
            this._init(args[0]);
            this.init(args[1]);
        } else if (this.initialization) {
            if (!this.currentTransition) {
                this.uuid = createUUID();
                this._enter(null, this.initialization, args[0]);
            } else {
                throw new Error('请勿重复调用初始化方法');
            }
        } else {
            throw new Error('请先初始化数据');
        }
    }

    public do(event: string, finish?: Function) {
        const transition = this._get(event);

        if (!transition) {
            return false;
        }

        this.uuid = createUUID();
        const oldTransition = this.currentTransition;
        this._leave(oldTransition, transition);
        this._enter(oldTransition, transition, finish);

        return true;
    }

    private _emit(prefix: string, state: string, data?: any, finish?: Function) {
        const event = `${prefix}:${state}`;
        if (!this.events[event]) {
            return finish && finish();
        }

        const eventArr = this.events[event];

        if (finish) {
            const uuid = this.uuid;
            const isValid = () => {
                return uuid === this.uuid;
            }

            let curr = 0;
            const max = eventArr.length;
            for (let index = 0; index < eventArr.length; index++) {
                const element = eventArr[index];
                if (element[2] === false) {
                    eventArr.splice(index--, 1);
                }

                let callFinish = false;
                const next = () => {
                    if (callFinish) return;
                    callFinish = true;

                    if (++curr === max) {
                        finish && finish(isValid());
                    }
                }
                if (element[0].call(element[1], data, next, isValid) !== 'ASYNC') {
                    next();
                }
            }
        } else {
            for (let index = 0; index < eventArr.length; index++) {
                const element = eventArr[index];
                if (element[2] === false) {
                    eventArr.splice(index--, 1);
                }

                element[0].call(element[1], data);
            }
        }
    }

    public on(event: string, callback: (data?: any, next?: Function, isValid?: Function) => any, target?: any) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push([callback, target, true]);
    }

    public once(event: string, callback: (data?: any, next?: Function, isValid?: Function) => any, target?: any) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push([callback, target, false]);
    }

    public off(event: string, callback: (data?: any, next?: Function, isValid?: Function) => any, target?: any) {
        if (!this.events[event]) {
            return;
        }
        for (let index = this.events[event].length - 1; index >= 0; index--) {
            const element = this.events[event][index];
            if (element[0] === callback && (target ? element[1] === target : true)) {
                this.events[event].splice(index, 1);
            }
        }
    }
}

const cache = {};

export function create(name: string, data?: StateMachineOption): StateMachine {
    cache[name] = new StateMachine(data);
    return cache[name];
}

export function get(name: string): StateMachine {
    return cache[name];
}

export function clear() {
    cc.js.clear(cache);
}

export function remove(name: string) {
    delete cache[name];
}